package org.chatty;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Control;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.TargetDataLine;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class RecordingSetup {

	private TargetDataLine targetDataLine_;
	private AudioFormat audioFormat_;

	public RecordingSetup() throws LineUnavailableException {
		this.autoDetect();
	}

	public RecordingSetup( TargetDataLine targetDataLine, AudioFormat audioFormat ) {
		this.setTargetDataLine( targetDataLine );
		this.setAudioFormat( audioFormat );
	}

	//// sounds junk

	public void autoDetect() throws LineUnavailableException {
		this.autoDetect( AudioSystem.getMixerInfo() );
	}

	public void open() throws LineUnavailableException {
		this.getTargetDataLine().open( this.getAudioFormat() );
		this.getTargetDataLine().start();
	}

	public int readInto( byte buffer[] ) {
		return this.getTargetDataLine().read( buffer, 0, buffer.length );
	}

	public void close() {
		this.getTargetDataLine().drain();
		this.getTargetDataLine().close();
	}

	//// boiler plate

	public TargetDataLine getTargetDataLine() {
		return this.targetDataLine_;
	}

	public void setTargetDataLine( TargetDataLine targetDataLine ) {
		this.targetDataLine_ = targetDataLine;
	}

	public AudioFormat getAudioFormat() {
		return this.audioFormat_;
	}

	public void setAudioFormat( AudioFormat audioFormat ) {
		this.audioFormat_ = audioFormat;
	}

	public String toString() {
		return this.toStringBuilder().toString();
	}

	public StringBuilder toStringBuilder() {
		return this.toStringBuilder( new StringBuilder() );
	}

	public StringBuilder toStringBuilder( StringBuilder stringBuilder ) {
		return stringBuilder
			.append( "<recordingSetup>" )
			.append( "<targetDataLine>" )
			.append( this.getTargetDataLine() )
			.append( "</targetDataLine>" )
			.append( "<audioFormat>" )
			.append( this.getAudioFormat() )
			.append( "</audioFormat>" )
			.append( "</recordingSetup>" )
		;
	}

	// you may need deep copies... sorry....
	public RecordingSetup copy( RecordingSetup that ) {
		this.setTargetDataLine( that.getTargetDataLine() );
		this.setAudioFormat( that.getAudioFormat() );
		return this;
	}

	///////////////////////

	private boolean autoDetect( Mixer.Info mixer_info_s[] ) throws LineUnavailableException {
		boolean success = false;
		for ( Mixer.Info mixer_info : mixer_info_s ) {
			success = this.autoDetect( AudioSystem.getMixer( mixer_info ) );
			if ( success ) break;
		}
		if ( !success ) throw new LineUnavailableException();
		return success;
	}

	private boolean autoDetect( Mixer mixer ) throws LineUnavailableException {
		return this.autoDetect( mixer, mixer.getTargetLineInfo() );
	}

	private boolean autoDetect( Mixer mixer, Line.Info line_info_s[] ) throws LineUnavailableException {
		boolean success = false;
		for ( Line.Info line_info : line_info_s ) {
			if ( line_info instanceof DataLine.Info ) {
				success = this.autoDetect( mixer, ( DataLine.Info ) line_info );
				if ( success ) break;
			}
		}
		return success;
	}

	private boolean autoDetect( Mixer mixer, DataLine.Info data_line_info ) throws LineUnavailableException {
		return this.autoDetect( 
			( TargetDataLine ) mixer.getLine( data_line_info )
			, data_line_info.getFormats() 
		);
	}

	private boolean autoDetect( TargetDataLine line, AudioFormat audio_format_s[] ) throws LineUnavailableException {
		boolean success = false;
		for ( AudioFormat audio_format : audio_format_s ) {
			int rates[] = new int[] { 8000,11025, 16000, 22050, 44100 };
			for ( int i = 0 ; i < rates.length && !success ; i++ ) {
				for ( int j = 0 ; j < rates.length && !success ; j++ ) {
					try {
						audio_format = this.autoDetect( line, audio_format, rates[ i ], rates[ j ] );
						this.setTargetDataLine( line );
						this.setAudioFormat( audio_format );
						success = true;
						break;
					} catch ( Exception e ) {
						//System.out.println( "fail.... " + e );
					}   
				}
			}
			if ( success ) break;
		}
		return success;
	}

	private AudioFormat autoDetect( TargetDataLine line, AudioFormat audio_format, int sample, int frame ) throws LineUnavailableException, javax.sound.sampled.UnsupportedAudioFileException, java.io.IOException {

		AudioFormat good = new AudioFormat(
			audio_format.getEncoding()
			, audio_format.getSampleRate() == -1 ? sample : audio_format.getSampleRate()
			, audio_format.getSampleSizeInBits()
			, audio_format.getChannels()
			, audio_format.getFrameSize()
			, audio_format.getFrameRate() == -1 ? frame : audio_format.getFrameRate()
			, audio_format.isBigEndian()
		);

		line.open( good );
		line.start();

		byte tempBuffer[] = new byte[ 1024 ];
		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

		int count = line.read( tempBuffer, 0, tempBuffer.length );
		if( count > 0 ) {
			byteArrayOutputStream.write( tempBuffer, 0, count );
		} else {
			if ( count < 0 ); // TODO: should we crap a brick?
		}

		line.drain();
		line.close();

		byteArrayOutputStream.flush();
		byteArrayOutputStream.close();

		return good;
	}
};
